;
; Copyright 2007, Dengg David, david-d@gmx.at. All rights reserved.
; Copyright 2008, Michael Pfeiffer, laplace@users.sourceforge.net. All rights reserved.
; Copyright 2005, Ingo Weinhold, bonefish@users.sf.net.
; Copyright 2011, Axel Dörfler, axeld@pinc-software.de.
; Distributed under the terms of the MIT License.


%assign USE_TEST_MENU				0

%assign BOOT_BLOCK_START_ADDRESS	0x7c00

%assign MBR_SIGNATURE				0xAA55

; BIOS calls

%assign BIOS_VIDEO_SERVICES			0x10
%assign BIOS_DISK_SERVICES			0x13
%assign BIOS_KEYBOARD_SERVICES		0x16
%assign BIOS_REBOOT					0x19		; dl - boot drive number
%assign BIOS_TIME_SERVICES			0x1A

; video services
%assign SET_VIDEO_MODE					0x00	; al - mode

%assign	SET_CURSOR_SHAPE				0x01	; ch - starting scan line (5 bits)
												; cl - ending scan line (5 bits)

%assign SET_CURSOR						0x02	; dl - column
												; dh - row
												; bh - page


%assign GET_CURSOR						0x03	; bh - page
												; -> dl - column
												;	dh - row
												;	Cursor shape:
												;	ch - starting scan line
												;	cl - ending scan line

%assign	SCROLL_UP						0x06	; al - lines (0: clear screen)
												; bh - attribute
												; ch - upper line
												; cl - left column
												; dh - lower line
												; dl - right column

%assign WRITE_CHAR						0x09	; al - char
												; bh - page
												; bl - attribute
												; cx - count

;%assign WRITE_CHAR						0x0e	; al - char
												; bh - page
												; bl - foreground color (graphics mode only)

; disk services
%assign READ_DISK_SECTORS				0x02	; dl	- drive
												; es:bx - buffer
												; dh	- head (0 - 15)
												; ch	- track 7:0 (0 - 1023)
												; cl	- track 9:8,
												;		 sector (1 - 17)
												; al	- sector count
												; -> al - sectors read
%assign READ_DRIVE_PARAMETERS			0x08	; dl - drive
												; -> cl - max cylinder 9:8
												;	   - sectors per track
												;	ch - max cylinder 7:0
												;	dh - max head
												;	dl - number of drives (?)
%assign CHECK_DISK_EXTENSIONS_PRESENT	0x41	; bx - 0x55aa
												; dl - drive
												; -> success: carry clear
												;	ah - extension version
												;	bx - 0xaa55
												;	cx - support bit mask
												; -> error: carry set
%assign EXTENDED_READ					0x42	; dl - drive
												; ds:si - address packet
												; -> success: carry clear
												; -> error: carry set

%assign FIXED_DISK_SUPPORT				0x1	 	; flag indicating fixed disk
												; extension command subset

; keyboard services
%assign READ_CHAR						0x00	; -> al - ASCII char
												;	ah - scan code

%assign	PROBE_CHAR						0x01	; -> zf = 0
												;	al - ASCII char
												;	ah - scan code

%assign	GET_MODIFIER_KEYS				0x02	;-> al - modifier key bitmask

; timer services
%assign READ_CLOCK						0x00	; -> cx	- high word
												;	dx - low word
												;	one tick = 1/18.2s

%assign	TICKS_PER_SECOND				19

; video modes
%assign GRAPHIC_MODE_80x25				0x12	; 640 x 480 graphic mode

%assign TEXT_COLUMNS					80		; Number of columns
%assign TEXT_ROWS						25		; Number of rows

; Colors
%assign	BLACK							0
%assign	BLUE							1
%assign	GREEN							2
%assign CYAN							3
%assign RED								4
%assign MAGENTA							5
%assign BROWN							6
%assign LIGHT_GRAY						7
%assign	DARK_GRAY						8
%assign	LIGHT_BLUE						9
%assign	LIGHT_GREEN						10
%assign	LIGHT_CYAN						11
%assign	LIGHT_RED						12
%assign	LIGHT_MAGENTA					13
%assign	YELLOW							14
%assign	WHITE							15

%assign BRIGHT_COLOR_MASK				8

; Characters
%assign	TRIANGLE_TO_RIGHT				16
%assign	TRIANGLE_TO_LEFT				17

; Key codes
%assign KEY_DOWN						0x50
%assign KEY_UP							0x48
%assign KEY_PAGE_DOWN					0x51
%assign KEY_PAGE_UP						0x49
%assign KEY_HOME						0x47
%assign KEY_END							0x4f
%assign KEY_RETURN						0x1C

; Modifier key bitmasks
%assign MODIFIER_RIGHT_SHIFT_KEY		0x01
%assign MODIFIER_LEFT_SHIFT_KEY			0x02
%assign MODIFIER_CONTROL_KEY			0x04
%assign MODIFIER_ALT_KEY				0x08
%assign MODIFIER_SCROLL_LOCK_KEY		0x10
%assign MODIFIER_NUM_LOCK_KEY			0x20
%assign MODIFIER_CAPS_LOCK_KEY			0x40
%assign MODIFIER_INSERT_KEY				0x80

; String constants with their length
%define TITLE							'Haiku Boot Manager'
%strlen TITLE_LENGTH					TITLE
%define SELECT_OS_MESSAGE				'Select an OS from the menu'
%strlen SELECT_OS_MESSAGE_LENGTH		SELECT_OS_MESSAGE

; 16 bit code
SECTION .text
BITS 16


; nicer way to get the size of a structure
%define sizeof(s)	s %+ _size

; using a structure in a another structure definition
%macro  nstruc  1-2	 1
					resb	sizeof(%1) * %2
%endmacro

; Variables on stack
struc	Locals
	selection		resw	1
	firstLine		resb	2 ; low byte used only
	timeoutTicks	resd	1
	cursorX			resb	1
	cursorY			resb	1
	cursorShape		resw	1
	biosDrive		resb	1
endstruc

cursorPosition		equ cursorX

%macro DEBUG_PAUSE 0
	push	ax
	mov		ah, READ_CHAR
	int		BIOS_KEYBOARD_SERVICES
	pop		ax
%endmacro

%macro CLEAR_SCREEN 0
	mov		ah, SCROLL_UP
	xor		al, al
	mov		bh, WHITE
	xor		cx, cx
	mov		dx, (TEXT_ROWS-1) * 0x100 + (TEXT_COLUMNS-1)
	int		BIOS_VIDEO_SERVICES
%endmacro

; Prints a null terminated string
; bl ... color
; si ... offset to string
%macro PRINT_STRING 0
	push	ax
	push	bx
	push	cx
	push	dx
	xor		bh, bh								; write on page 0
	jmp		.loop_condition
.loop:
	mov		dx, [bp + cursorPosition]
	mov		ah, SET_CURSOR
	int		BIOS_VIDEO_SERVICES

	inc		byte [bp + cursorX]

	mov		cx, 1
	mov		ah, WRITE_CHAR
	int		BIOS_VIDEO_SERVICES
.loop_condition:
	lodsb
	cmp		al, 0
	jnz		.loop
	pop		dx
	pop		cx
	pop		bx
	pop		ax
	ret
%endmacro

; 64 bit value
struc   quadword
	.lower			resd	1
	.upper			resd	1
endstruc

; address packet as required by the EXTENDED_READ BIOS call
struc   AddressPacket
	.packet_size	resb	1
	.reserved1		resb	1
	.block_count	resb	1
	.reserved2		resb	1
	.buffer			resd	1
	.offset			nstruc	quadword
endstruc

struc	BootLoaderAddress
	.device			resb	1			; hard drive number
	.offset			nstruc	quadword	; LBA of start start sector
endstruc

; use code available in stage 1
%define printstr printStringStage1

stage1:
	mov		ax, 0x07c0						; BOOT_BLOCK_START_ADDRESS / 16
	mov		ds, ax							; Setup segment registers
	mov		es, ax
	mov		ss, ax

	mov		sp, 0xFFFF - sizeof(Locals)		; Make stack empty
	mov		bp, sp

	mov		[bp + biosDrive], dl			; Store boot drive
	cld										; String operations increment index
											; registers
	CLEAR_SCREEN
	call	hideCursor

	mov		bh, 0							; Text output on page 0

	; Print title centered at row 2
	mov		dx, 1 * 0x100 + (40 - TITLE_LENGTH / 2)
	mov		[bp + cursorPosition], dx

	mov		si, kTitle
	mov		bl, WHITE
	call	printstr

	; Print message centered at second last row
	mov		dx, (TEXT_ROWS-2) * 0x100 + (40 - SELECT_OS_MESSAGE_LENGTH / 2)
	mov		[bp + cursorPosition], dx

	mov		bl, LIGHT_GRAY
	mov		si, kSelectOSMessage
	call	printstr

	; Chain load rest of boot loader
	mov		ah, EXTENDED_READ				; Load 3 more sectors
	mov		dl, [bp + biosDrive]
	mov		si, nextStageDAP
	int		BIOS_DISK_SERVICES
	jc		.error							; I/O error
	jmp		stage2							; Continue in loaded stage 2

.error:
	call	showCursor
	mov		si, kError
	mov		bl, RED
	call	printstr

	mov		ah, READ_CHAR
	int		BIOS_KEYBOARD_SERVICES

	mov		dl, [bp + biosDrive]
	int		BIOS_REBOOT

printStringStage1:
	PRINT_STRING

hideCursor:
	mov		ah, GET_CURSOR
	int		BIOS_VIDEO_SERVICES
	mov		[bp + cursorShape], cx

	mov		ah, SET_CURSOR_SHAPE
	mov		cx, 0x2000
	int		BIOS_VIDEO_SERVICES
	ret

showCursor:
	mov		cx, [bp + cursorShape]
	mov		ah, SET_CURSOR_SHAPE
	int		BIOS_VIDEO_SERVICES
	ret

nextStageDAP:
	istruc AddressPacket
		at AddressPacket.packet_size,	db		0x10
		at AddressPacket.block_count,	db		0x03
		at AddressPacket.buffer,		dw		0x0200, 0x07c0
		at AddressPacket.offset,		dw		1
	iend

kTitle:
	db		TITLE, 0x00
kSelectOSMessage:
	db		SELECT_OS_MESSAGE, 0x00
kError:
	db		'Error loading sectors!', 0x00

kStage1UnusedSpace	equ	440 - ($-$$)
	; Fill the missing space to reach byte 440
	times kStage1UnusedSpace db 'B'

kDiskSignature:
	dw		0, 0
kReserved:
	dw		0
kPartitionTable:
	times	64 db 0

kMBRSignature:
	; Magic marker "AA55" (to identify a valid boot record)
	dw		MBR_SIGNATURE

; ======================================================================
; ======================= SECOND SECTOR ================================
; ======================================================================

; Use code available in stage 2
%define printstr printStringStage2

%assign	TIMEOUT_OFF		0xffff


stage2:
	mov		ax, [defaultItem]					; Select default item
	mov		[bp + selection], ax

	mov		ax, TICKS_PER_SECOND				; Calculate timeout ticks
	mul		word [timeout]
	mov		bx, dx
	push	ax

	mov		ah, READ_CLOCK
	int		BIOS_TIME_SERVICES

	pop		ax									; Add current ticks
	add		ax, dx
	adc		bx, cx
	mov		[bp + timeoutTicks], ax
	mov		[bp + timeoutTicks + 2], bx

	mov		al, [listItemCount]					; Calculate start row for menu
	shr		al, 1
	mov		bl, TEXT_ROWS / 2
	sub		bl, al								; y = TEXT_ROWS / 2 - number of items / 2
	mov		[bp + firstLine], bl

	mov		ah, GET_MODIFIER_KEYS				; Disable timeout if ALT key is pressed
	int		BIOS_KEYBOARD_SERVICES
	and		al, MODIFIER_ALT_KEY
	jz		showMenu
	mov		word [timeout], TIMEOUT_OFF

showMenu:
	call	printMenu

	cmp		word [timeout], TIMEOUT_OFF
	je		inputLoop

timeoutLoop:
	mov		ah, PROBE_CHAR
	int		BIOS_KEYBOARD_SERVICES
	jnz		inputLoop							; cancel timeout if key is pressed
	call	isTimeoutReached
	jnc		timeoutLoop
	jmp		bootSelectedPartition

isTimeoutReached:
	mov		ah, READ_CLOCK
	int		BIOS_TIME_SERVICES
	cmp		cx, [bp + timeoutTicks + 2]
	jb		.returnFalse
	ja		.returnTrue
	cmp		dx, [bp + timeoutTicks]
	ja		.returnTrue
.returnFalse:
	clc
	ret
.returnTrue:
	stc
	ret

; ================== Wait for a key and do something with it ==================
mainLoop:
	call	printMenu

inputLoop:
	mov		ah, READ_CHAR
	int		BIOS_KEYBOARD_SERVICES				; AL = ASCII Code, AH = Scancode

	cmp		ah, KEY_DOWN
	je		selectNextPartition

	cmp		ah, KEY_PAGE_DOWN
	je		selectLastPartition
	cmp		ah, KEY_END
	je		selectLastPartition

	cmp		ah, KEY_UP
	je		selectPreviousPartition

	cmp		ah, KEY_PAGE_UP
	je		selectFirstPartition
	cmp		ah, KEY_HOME
	je		selectFirstPartition

	cmp		ah, KEY_RETURN
	jne		inputLoop
	jmp		bootSelectedPartition

selectNextPartition:
	mov		ax, [bp + selection]
	inc		ax
	cmp		ax, [listItemCount]
	jne		.done								; At end of list?
	xor		ax, ax								; Then jump to first entry
.done:
	mov		[bp + selection], ax
	jmp		mainLoop

selectLastPartition:
	mov		ax, [listItemCount]
	dec		ax
	mov		[bp + selection], ax
	jmp		mainLoop

selectPreviousPartition:
	mov		ax, [bp + selection]
	or		ax, ax
	jnz		.done								; At top of list?
	mov		ax, [listItemCount]					; Then jump to last entry
.done:
	dec		ax
	mov		[bp + selection], ax
	jmp		mainLoop

selectFirstPartition:
	xor		ax, ax
	mov		[bp + selection], ax
	jmp		mainLoop


; ======================= Print the OS list ============================
printMenu:
	mov		al, [bp + firstLine]
	mov		[bp + cursorY], al

	mov		si, list							; Start at top of list
	xor		cx, cx								; The index of the current item

.loop:
	lodsb										; String length incl. 0-terminator
	add		al, 3								; center menu item
	shr		al, 1								; x = TEXT_COLUMNS / 2 - length / 2
	mov		dl, TEXT_COLUMNS / 2
	sub		dl, al
	mov		[bp + cursorX], dl

	mov		al, TRIANGLE_TO_RIGHT
	call	updateMarker
	inc		byte [bp + cursorX]

	mov		di, cx
	and		di, 3
	mov		bl, [kColorTable + di]				; Text color

	cmp		cx, [bp + selection]
	jne		.print								; Selected item reached?
	xor		bl, BRIGHT_COLOR_MASK				; Highlight it

.print:
	call	printstr
	add		si, sizeof(BootLoaderAddress)

	add		byte [bp + cursorX], 1
	mov		al, TRIANGLE_TO_LEFT
	call	updateMarker

	inc		byte [bp + cursorY]
	inc		cx

	cmp		cx, [listItemCount]
	jne		.loop
	ret

updateMarker:
	cmp		cx, [bp + selection]
	je		.print
	mov		al, ' '								; Clear marker
.print:
	mov		bl, WHITE
	jmp		printChar							; return from subroutine


; ========================== Chainload ==========================

bootSelectedPartition:

	call	showCursor

	call	getSelectedBootLoaderAddress
	lodsb										; Set boot drive
	mov		dl, al

	mov		di, bootSectorDAP+AddressPacket.offset	; Copy start sector
	mov		cx, 4								; It is stored in a quad word
.copy_start_sector:
	lodsw
	stosw
	loop .copy_start_sector

	mov		ah, EXTENDED_READ					; Now read start sector from HD
	mov		si, bootSectorDAP
	int		BIOS_DISK_SERVICES
	mov		si, kReadError
	jc		printAndHalt						; Failed to read sector

	mov		ax, [kMBRSignature]
	cmp		ax, MBR_SIGNATURE
	mov		si, kNoBootablePartitionError
	jne		printAndHalt						; Missing signature

	CLEAR_SCREEN

	; Print "Loading <name>" at top of screen
	mov		word [bp + cursorPosition], 0
	mov		si, kLoadingMessage
	mov		bl, LIGHT_GRAY
	call	printstr

	inc		byte [bp + cursorX]
	call	getSelectedMenuItem
	inc		si									; Skip string length byte
	call	printstr

	mov		dx, 0x100
	xor		bh, bh
	mov		ah, SET_CURSOR
	int		BIOS_VIDEO_SERVICES

	call	getSelectedBootLoaderAddress
	mov		dl, [si]							; drive number in dl

	jmp		$$									; Start loaded boot loader


printAndHalt:
	mov		dx, (TEXT_ROWS-4) * 0x100 + (TEXT_COLUMNS / 3)
	mov		[bp + cursorPosition], dx

	mov		bx, 0x0F							; Page number and foreground color
	call	printstr
	mov		ah, READ_CHAR
	int		BIOS_KEYBOARD_SERVICES
	mov		dl, [bp + biosDrive]
	int		BIOS_REBOOT

; Output:
;	si	address of selected menu item
; Trashes:
;	ax, cx
getSelectedMenuItem:
	mov		si, list							; Search address of start sector
												; of the selected item.
	mov		cx, [bp + selection]
	inc		cx									; Number of required iterations

	xor		ah, ah								; The high-byte of the string length
												; see loop body
	jmp		.entry

.loop:
	lodsb										; Length of menu item name
	add		si, ax								; Skip name to BootLoaderAddess
	add		si, sizeof(BootLoaderAddress)

.entry:
	loop	.loop
	ret

getSelectedBootLoaderAddress:
	call	getSelectedMenuItem
	lodsb
	xor		ah, ah
	add		si, ax								; Skip name
	mov		dl, [si]
	test	dl, 0								; if drive is 0, use boot drive
	jz		.takeOverBootDrive
	ret
.takeOverBootDrive:
	mov		dl, [bp + biosDrive]
	mov		[si], dl
	ret

printStringStage2:
	PRINT_STRING

; al ... ASCII character
; bl ... color
printChar:
	push	ax
	push	bx
	push	cx
	push	dx

	xor		bh, bh								; Write on page 0

	mov		dx, [bp + cursorPosition]
	mov		ah, SET_CURSOR
	int		BIOS_VIDEO_SERVICES

	inc		byte [bp + cursorX]

	mov		cx, 1
	mov		ah, WRITE_CHAR
	int		BIOS_VIDEO_SERVICES

	pop		dx
	pop		cx
	pop		bx
	pop		ax
	ret

; ================================ DATA ===========================

bootSectorDAP:
	istruc AddressPacket
		at AddressPacket.packet_size,	db		0x10
		at AddressPacket.block_count,	db		0x01
		at AddressPacket.buffer,		dw		0x0000, 0x07c0
	iend

kColorTable:
	db BLUE, RED, GREEN, CYAN
kReadError:
	db		'Error loading sectors', 0x00
kNoBootablePartitionError:
	db		'Not a bootable partition', 0x00
kLoadingMessage:
	db		'Loading', 0x00


listItemCount:
defaultItem			equ		listItemCount + 2
timeout				equ		defaultItem + 2
list				equ		timeout + 2

; dw number of entries
; dw the default entry
; dw the timeout (-1 for none)
; entry:
; db size of partition name 0-terminated string
; db 0-terminated string with partition name
; db hard drive number
; quadword start sector

%if USE_TEST_MENU
	dw		0x06

	dw		2

	dw		5

	db		0x06
	db		'HAIKU', 0
	db		0x80
	dw		1, 0, 0, 0

	db		0x08
	db		'FreeBSD', 0
	db		0x80
	dw		0x003F, 0, 0, 0

	db		0x04
	db		'DOS', 0
	db		0x80
	dw		0x003E, 0, 0, 0

	db		0x06
	db		'LINUX', 0
	db		0x80
	dw		0x003F, 0, 0, 0

	db		0x08
	db		'BeOS R5', 0
	db		0x80
	dw		0x003F, 0, 0, 0

	db		0x07
	db		'OpenBSD', 0
	db		0x80
	dw		0xAAAA, 0, 0, 0

	dw		kStage1UnusedSpace
%endif

